/** @file
  The AhciPei driver is used to manage ATA hard disk device working under AHCI
  mode at PEI phase.

  Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "AhciPei.h"
#include <Ppi/PciDevice.h>
#include <Library/DevicePathLib.h>
#include <IndustryStandard/Pci.h>

EFI_PEI_PPI_DESCRIPTOR  mAhciAtaPassThruPpiListTemplate = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEdkiiPeiAtaPassThruPpiGuid,
  NULL
};

EFI_PEI_PPI_DESCRIPTOR  mAhciBlkIoPpiListTemplate = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiPeiVirtualBlockIoPpiGuid,
  NULL
};

EFI_PEI_PPI_DESCRIPTOR  mAhciBlkIo2PpiListTemplate = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiPeiVirtualBlockIo2PpiGuid,
  NULL
};

EFI_PEI_PPI_DESCRIPTOR  mAhciStorageSecurityPpiListTemplate = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEdkiiPeiStorageSecurityCommandPpiGuid,
  NULL
};

EFI_PEI_NOTIFY_DESCRIPTOR  mAhciEndOfPeiNotifyListTemplate = {
  (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiEndOfPeiSignalPpiGuid,
  AhciPeimEndOfPei
};

EFI_PEI_NOTIFY_DESCRIPTOR  mAtaAhciHostControllerNotify = {
  (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEdkiiPeiAtaAhciHostControllerPpiGuid,
  AtaAhciHostControllerPpiInstallationCallback
};

EFI_PEI_NOTIFY_DESCRIPTOR  mPciDevicePpiNotify = {
  (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEdkiiPeiPciDevicePpiGuid,
  AtaAhciPciDevicePpiInstallationCallback
};

/**
  Free the DMA resources allocated by an ATA AHCI controller.

  @param[in] Private    A pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA data
                        structure.

**/
VOID
AhciFreeDmaResource (
  IN PEI_AHCI_CONTROLLER_PRIVATE_DATA  *Private
  )
{
  EFI_AHCI_REGISTERS  *AhciRegisters;

  ASSERT (Private != NULL);

  AhciRegisters = &Private->AhciRegisters;

  if (AhciRegisters->AhciRFisMap != NULL) {
    IoMmuFreeBuffer (
      EFI_SIZE_TO_PAGES (AhciRegisters->MaxRFisSize),
      AhciRegisters->AhciRFis,
      AhciRegisters->AhciRFisMap
      );
  }

  if (AhciRegisters->AhciCmdListMap != NULL) {
    IoMmuFreeBuffer (
      EFI_SIZE_TO_PAGES (AhciRegisters->MaxCmdListSize),
      AhciRegisters->AhciCmdList,
      AhciRegisters->AhciCmdListMap
      );
  }

  if (AhciRegisters->AhciCmdTableMap != NULL) {
    IoMmuFreeBuffer (
      EFI_SIZE_TO_PAGES (AhciRegisters->MaxCmdTableSize),
      AhciRegisters->AhciCmdTable,
      AhciRegisters->AhciCmdTableMap
      );
  }
}

/**
  One notified function to cleanup the allocated DMA buffers at EndOfPei.

  @param[in] PeiServices         Pointer to PEI Services Table.
  @param[in] NotifyDescriptor    Pointer to the descriptor for the Notification
                                 event that caused this function to execute.
  @param[in] Ppi                 Pointer to the PPI data associated with this function.

  @retval EFI_SUCCESS    The function completes successfully

**/
EFI_STATUS
EFIAPI
AhciPeimEndOfPei (
  IN EFI_PEI_SERVICES           **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
  )
{
  PEI_AHCI_CONTROLLER_PRIVATE_DATA  *Private;

  Private = GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_NOTIFY (NotifyDescriptor);
  AhciFreeDmaResource (Private);

  return EFI_SUCCESS;
}

/**
  Initialize and install PrivateData PPIs.

  @param[in] MmioBase            MMIO base address of specific AHCI controller
  @param[in] DevicePath          A pointer to the EFI_DEVICE_PATH_PROTOCOL
                                 structure.
  @param[in] DevicePathLength    Length of the device path.

  @retval EFI_SUCCESS  AHCI controller initialized and PPIs installed
  @retval others       Failed to initialize AHCI controller
**/
EFI_STATUS
AtaAhciInitPrivateData (
  IN UINTN                     MmioBase,
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN UINTN                     DevicePathLength
  )
{
  EFI_STATUS                        Status;
  UINT32                            PortBitMap;
  UINT8                             NumberOfPorts;
  PEI_AHCI_CONTROLLER_PRIVATE_DATA  *Private;
  EFI_BOOT_MODE                     BootMode;

  DEBUG ((DEBUG_INFO, "Initializing private data for ATA\n"));

  //
  // Get the current boot mode.
  //
  Status = PeiServicesGetBootMode (&BootMode);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Fail to get the current boot mode.\n", __func__));
    return Status;
  }

  //
  // Check validity of the device path of the ATA AHCI controller.
  //
  Status = AhciIsHcDevicePathValid (DevicePath, DevicePathLength);
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "%a: The device path is invalid.\n",
      __func__
      ));
    return Status;
  }

  //
  // For S3 resume performance consideration, not all ports on an ATA AHCI
  // controller will be enumerated/initialized. The driver consumes the
  // content within S3StorageDeviceInitList LockBox to get the ports that
  // will be enumerated/initialized during S3 resume.
  //
  if (BootMode == BOOT_ON_S3_RESUME) {
    NumberOfPorts = AhciS3GetEumeratePorts (DevicePath, DevicePathLength, &PortBitMap);
    if (NumberOfPorts == 0) {
      return EFI_SUCCESS;
    }
  } else {
    PortBitMap = MAX_UINT32;
  }

  //
  // Memory allocation for controller private data.
  //
  Private = AllocateZeroPool (sizeof (PEI_AHCI_CONTROLLER_PRIVATE_DATA));
  if (Private == NULL) {
    DEBUG ((
      DEBUG_ERROR,
      "%a: Fail to allocate private data.\n",
      __func__
      ));
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Initialize controller private data.
  //
  Private->Signature        = AHCI_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE;
  Private->MmioBase         = MmioBase;
  Private->DevicePathLength = DevicePathLength;
  Private->DevicePath       = DevicePath;
  Private->PortBitMap       = PortBitMap;
  InitializeListHead (&Private->DeviceList);

  Status = AhciModeInitialization (Private);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Private->AtaPassThruMode.Attributes = EFI_ATA_PASS_THRU_ATTRIBUTES_PHYSICAL |
                                        EFI_ATA_PASS_THRU_ATTRIBUTES_LOGICAL;
  Private->AtaPassThruMode.IoAlign      = sizeof (UINTN);
  Private->AtaPassThruPpi.Revision      = EDKII_PEI_ATA_PASS_THRU_PPI_REVISION;
  Private->AtaPassThruPpi.Mode          = &Private->AtaPassThruMode;
  Private->AtaPassThruPpi.PassThru      = AhciAtaPassThruPassThru;
  Private->AtaPassThruPpi.GetNextPort   = AhciAtaPassThruGetNextPort;
  Private->AtaPassThruPpi.GetNextDevice = AhciAtaPassThruGetNextDevice;
  Private->AtaPassThruPpi.GetDevicePath = AhciAtaPassThruGetDevicePath;
  CopyMem (
    &Private->AtaPassThruPpiList,
    &mAhciAtaPassThruPpiListTemplate,
    sizeof (EFI_PEI_PPI_DESCRIPTOR)
    );
  Private->AtaPassThruPpiList.Ppi = &Private->AtaPassThruPpi;
  PeiServicesInstallPpi (&Private->AtaPassThruPpiList);

  Private->BlkIoPpi.GetNumberOfBlockDevices = AhciBlockIoGetDeviceNo;
  Private->BlkIoPpi.GetBlockDeviceMediaInfo = AhciBlockIoGetMediaInfo;
  Private->BlkIoPpi.ReadBlocks              = AhciBlockIoReadBlocks;
  CopyMem (
    &Private->BlkIoPpiList,
    &mAhciBlkIoPpiListTemplate,
    sizeof (EFI_PEI_PPI_DESCRIPTOR)
    );
  Private->BlkIoPpiList.Ppi = &Private->BlkIoPpi;
  PeiServicesInstallPpi (&Private->BlkIoPpiList);

  Private->BlkIo2Ppi.Revision                = EFI_PEI_RECOVERY_BLOCK_IO2_PPI_REVISION;
  Private->BlkIo2Ppi.GetNumberOfBlockDevices = AhciBlockIoGetDeviceNo2;
  Private->BlkIo2Ppi.GetBlockDeviceMediaInfo = AhciBlockIoGetMediaInfo2;
  Private->BlkIo2Ppi.ReadBlocks              = AhciBlockIoReadBlocks2;
  CopyMem (
    &Private->BlkIo2PpiList,
    &mAhciBlkIo2PpiListTemplate,
    sizeof (EFI_PEI_PPI_DESCRIPTOR)
    );
  Private->BlkIo2PpiList.Ppi = &Private->BlkIo2Ppi;
  PeiServicesInstallPpi (&Private->BlkIo2PpiList);

  if (Private->TrustComputingDevices != 0) {
    DEBUG ((
      DEBUG_INFO,
      "%a: Security Security Command PPI will be produced.\n",
      __func__
      ));
    Private->StorageSecurityPpi.Revision           = EDKII_STORAGE_SECURITY_PPI_REVISION;
    Private->StorageSecurityPpi.GetNumberofDevices = AhciStorageSecurityGetDeviceNo;
    Private->StorageSecurityPpi.GetDevicePath      = AhciStorageSecurityGetDevicePath;
    Private->StorageSecurityPpi.ReceiveData        = AhciStorageSecurityReceiveData;
    Private->StorageSecurityPpi.SendData           = AhciStorageSecuritySendData;
    CopyMem (
      &Private->StorageSecurityPpiList,
      &mAhciStorageSecurityPpiListTemplate,
      sizeof (EFI_PEI_PPI_DESCRIPTOR)
      );
    Private->StorageSecurityPpiList.Ppi = &Private->StorageSecurityPpi;
    PeiServicesInstallPpi (&Private->StorageSecurityPpiList);
  }

  CopyMem (
    &Private->EndOfPeiNotifyList,
    &mAhciEndOfPeiNotifyListTemplate,
    sizeof (EFI_PEI_NOTIFY_DESCRIPTOR)
    );
  PeiServicesNotifyPpi (&Private->EndOfPeiNotifyList);

  return EFI_SUCCESS;
}

/**
  Initialize AHCI controller from EDKII_ATA_AHCI_HOST_CONTROLLER_PPI instance.

  @param[in] AhciHcPpi  Pointer to the AHCI Host Controller PPI instance.

  @retval EFI_SUCCESS   PPI successfully installed.
**/
EFI_STATUS
AtaAhciInitPrivateDataFromHostControllerPpi (
  IN EDKII_ATA_AHCI_HOST_CONTROLLER_PPI  *AhciHcPpi
  )
{
  UINT8                     Controller;
  UINTN                     MmioBase;
  UINTN                     DevicePathLength;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  EFI_STATUS                Status;

  Controller = 0;
  MmioBase   = 0;
  while (TRUE) {
    Status = AhciHcPpi->GetAhciHcMmioBar (
                          AhciHcPpi,
                          Controller,
                          &MmioBase
                          );
    //
    // When status is error, meant no controller is found.
    //
    if (EFI_ERROR (Status)) {
      break;
    }

    Status = AhciHcPpi->GetAhciHcDevicePath (
                          AhciHcPpi,
                          Controller,
                          &DevicePathLength,
                          &DevicePath
                          );
    if (EFI_ERROR (Status)) {
      DEBUG ((
        DEBUG_ERROR,
        "%a: Fail to allocate get the device path for Controller %d.\n",
        __func__,
        Controller
        ));
      return Status;
    }

    Status = AtaAhciInitPrivateData (MmioBase, DevicePath, DevicePathLength);
    if (EFI_ERROR (Status)) {
      DEBUG ((
        DEBUG_ERROR,
        "%a: Controller initialization fail for Controller %d with Status - %r.\n",
        __func__,
        Controller,
        Status
        ));
    } else {
      DEBUG ((
        DEBUG_INFO,
        "%a: Controller %d has been successfully initialized.\n",
        __func__,
        Controller
        ));
    }

    Controller++;
  }

  return EFI_SUCCESS;
}

/**
  Callback for EDKII_ATA_AHCI_HOST_CONTROLLER_PPI installation.

  @param[in] PeiServices         Pointer to PEI Services Table.
  @param[in] NotifyDescriptor    Pointer to the descriptor for the Notification
                                 event that caused this function to execute.
  @param[in] Ppi                 Pointer to the PPI data associated with this function.

  @retval EFI_SUCCESS            The function completes successfully
  @retval Others                 Cannot initialize AHCI controller from given EDKII_ATA_AHCI_HOST_CONTROLLER_PPI

**/
EFI_STATUS
EFIAPI
AtaAhciHostControllerPpiInstallationCallback (
  IN EFI_PEI_SERVICES           **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
  )
{
  EDKII_ATA_AHCI_HOST_CONTROLLER_PPI  *AhciHcPpi;

  if (Ppi == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  AhciHcPpi = (EDKII_ATA_AHCI_HOST_CONTROLLER_PPI *)Ppi;

  return AtaAhciInitPrivateDataFromHostControllerPpi (AhciHcPpi);
}

/**
  Initialize AHCI controller from fiven PCI_DEVICE_PPI.

  @param[in] PciDevice  Pointer to the PCI Device PPI instance.

  @retval EFI_SUCCESS      The function completes successfully
  @retval Others           Cannot initialize AHCI controller for given device
**/
EFI_STATUS
AtaAhciInitPrivateDataFromPciDevice (
  EDKII_PCI_DEVICE_PPI  *PciDevice
  )
{
  EFI_STATUS                Status;
  PCI_TYPE00                PciData;
  UINT32                    MmioBase;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  UINTN                     DevicePathLength;
  UINT64                    EnabledPciAttributes;

  //
  // Now further check the PCI header: Base Class (offset 0x0B) and
  // Sub Class (offset 0x0A). This controller should be an SATA controller
  //
  Status = PciDevice->PciIo.Pci.Read (
                                  &PciDevice->PciIo,
                                  EfiPciIoWidthUint8,
                                  PCI_CLASSCODE_OFFSET,
                                  sizeof (PciData.Hdr.ClassCode),
                                  PciData.Hdr.ClassCode
                                  );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  if (!IS_PCI_IDE (&PciData) && !IS_PCI_SATADPA (&PciData)) {
    return EFI_UNSUPPORTED;
  }

  Status = PciDevice->PciIo.Attributes (
                              &PciDevice->PciIo,
                              EfiPciIoAttributeOperationSupported,
                              0,
                              &EnabledPciAttributes
                              );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  } else {
    EnabledPciAttributes &= (UINT64)EFI_PCI_DEVICE_ENABLE;
    Status                = PciDevice->PciIo.Attributes (
                                               &PciDevice->PciIo,
                                               EfiPciIoAttributeOperationEnable,
                                               EnabledPciAttributes,
                                               NULL
                                               );
    if (EFI_ERROR (Status)) {
      return EFI_UNSUPPORTED;
    }
  }

  Status = PciDevice->PciIo.Pci.Read (
                                  &PciDevice->PciIo,
                                  EfiPciIoWidthUint32,
                                  0x24,
                                  1,
                                  &MmioBase
                                  );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  MmioBase &= 0xFFFFFFF0;

  DevicePathLength = GetDevicePathSize (PciDevice->DevicePath);
  DevicePath       = PciDevice->DevicePath;

  Status = AtaAhciInitPrivateData (MmioBase, DevicePath, DevicePathLength);
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_INFO,
      "%a: Failed to init controller, with Status - %r\n",
      __func__,
      Status
      ));
  }

  return EFI_SUCCESS;
}

/**
  Callback for EDKII_PCI_DEVICE_PPI installation.

  @param[in] PeiServices         Pointer to PEI Services Table.
  @param[in] NotifyDescriptor    Pointer to the descriptor for the Notification
                                 event that caused this function to execute.
  @param[in] Ppi                 Pointer to the PPI data associated with this function.

  @retval EFI_SUCCESS            The function completes successfully
  @retval Others                 Cannot initialize AHCI controller from given PCI_DEVICE_PPI

**/
EFI_STATUS
EFIAPI
AtaAhciPciDevicePpiInstallationCallback (
  IN EFI_PEI_SERVICES           **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
  )
{
  EDKII_PCI_DEVICE_PPI  *PciDevice;

  PciDevice = (EDKII_PCI_DEVICE_PPI *)Ppi;

  return AtaAhciInitPrivateDataFromPciDevice (PciDevice);
}

/**
  Entry point of the PEIM.

  @param[in] FileHandle     Handle of the file being invoked.
  @param[in] PeiServices    Describes the list of possible PEI Services.

  @retval EFI_SUCCESS    PPI successfully installed.

**/
EFI_STATUS
EFIAPI
AtaAhciPeimEntry (
  IN EFI_PEI_FILE_HANDLE     FileHandle,
  IN CONST EFI_PEI_SERVICES  **PeiServices
  )
{
  DEBUG ((DEBUG_INFO, "%a: Enters.\n", __func__));

  PeiServicesNotifyPpi (&mAtaAhciHostControllerNotify);

  PeiServicesNotifyPpi (&mPciDevicePpiNotify);

  return EFI_SUCCESS;
}
